contents
프로그래밍에서 매우 중요한 블로킹(blocking)/논블로킹(non-blocking) 과 동기(synchronous)/비동기(asynchronous) 개념에 대해 알아보겠습니다.
프로그래밍에서 이 개념들은 특히 파일 읽기, 네트워크 통신, 데이터베이스 조회 같은 입출력(I/O) 작업을 처리할 때 프로그램이 얼마나 효율적이고 빠르게 반응하는지를 결정하는 핵심 요소입니다.
기본 개념: 프로그램 실행과 I/O
프로그램을 주방에서 일하는 셰프라고 상상해 보세요. 셰프는 레시피(코드)에 적힌 순서대로 일을 처리합니다. 야채를 써는 것처럼 바로 끝나는 일도 있지만, 케이크를 굽거나 배달을 기다리는 것처럼 시간이 오래 걸리는 일도 있죠.
프로그래밍에서 I/O 작업은 바로 이 "기다리는" 일에 해당합니다. 프로그램이 외부 자원(디스크, 네트워크 등)에 데이터를 요청하면, 그 응답이 올 때까지 기다려야 합니다. 바로 이 기다리는 방식의 차이에서 블로킹/논블로킹과 동기/비동기 개념이 등장합니다.
블로킹 (Blocking) vs. 논블로킹 (Non-Blocking)
이 구분은 작업을 기다리는 동안 프로그램(또는 실행 스레드)이 무엇을 하는지에 대한 이야기입니다.
블로킹 (Blocking) 🚫
블로킹 방식은 특정 작업이 완전히 끝날 때까지 프로그램(해당 스레드)이 실행을 멈추고 기다리는 것을 의미합니다. 블로킹된 작업이 결과를 반환할 때까지 그 스레드에서는 다른 어떤 코드도 실행될 수 없습니다.
-
비유: 당신(셰프)이 케이크를 오븐에 넣었습니다. 그리고 케이크가 다 구워질 때까지 오븐 문 앞에서 아무것도 안 하고 계속 서서 기다립니다. 야채를 썰거나 다른 주문을 받는 등 어떤 다른 일도 할 수 없습니다.
-
특징:
-
순차적 실행: 작업이 하나씩 순서대로 실행됩니다. 하나의 작업이 오래 걸리면 그 뒤의 모든 작업이 지연됩니다.
-
단순함: 코드의 흐름이 직관적이라 작성하고 이해하기 쉽습니다.
-
리소스 비효율: I/O를 기다리는 동안 CPU는 아무 일도 하지 않으므로 리소스가 낭비될 수 있습니다.
-
응답성 문제: 특히 단일 스레드 GUI 애플리케이션에서 블로킹 I/O가 발생하면 프로그램 전체가 멈춰버리는 '프리징(freezing)' 현상이 나타납니다.
-
-
예시 (의사 코드):
function 서버에서_이미지_로드하기(imageUrl): // 이 함수는 블로킹 방식으로 동작합니다. 프로그램은 여기서 멈춰서 이미지를 기다립니다. image_data = downloadImage(imageUrl) return image_data print("프로그램 시작...") image = 서버에서_이미지_로드하기("http://example.com/large_image.jpg") // 프로그램이 여기서 멈춤 print("이미지 다운로드 완료!") displayImage(image) print("프로그램 종료.")이 코드에서 "프로그램 시작..."이 출력된 후,
downloadImage가 끝날 때까지 프로그램은 아무것도 하지 않고 멈춰 있습니다. 다운로드가 모두 완료되어야만 "이미지 다운로드 완료!"와 그 이후의 코드들이 실행됩니다. -
시각적 표현:

논블로킹 (Non-Blocking) ✅
논블로킹 방식은 작업을 시작시킨 후, 그 작업이 끝날 때까지 기다리지 않고 즉시 다음 코드를 실행하는 것을 의미합니다. 요청한 작업은 백그라운드에서 실행되며, 프로그램은 나중에 작업이 끝났는지 확인하거나 완료 통보를 받아야 합니다.
-
비유: 당신(셰프)이 케이크를 오븐에 넣었습니다. 기다리는 대신 타이머를 맞춰두고 즉시 다른 일(야채 썰기, 소스 만들기 등)을 시작합니다. 타이머가 울리면 케이크가 다 구워졌다는 것을 알게 됩니다.
-
특징:
-
동시 실행 (단일 스레드 내): 하나의 스레드가 여러 I/O 작업을 동시에 처리할 수 있게 해줍니다.
-
응답성: 애플리케이션이 멈추지 않고 계속 반응하므로 사용자 경험이 향상됩니다.
-
복잡성: 여러 작업의 상태를 관리하고, 결과가 준비되었을 때 이를 처리할 메커니즘(콜백, 프로미스 등)이 필요해 코드가 복잡해질 수 있습니다.
-
효율적인 리소스 사용: I/O를 기다리는 동안 CPU가 다른 일을 할 수 있어 리소스를 최대한 활용합니다.
-
-
예시 (개념적 의사 코드):
function 이미지_다운로드_시작(imageUrl, callbackFunction): // 백그라운드에서 다운로드를 시작하고 즉시 리턴합니다. // 다운로드가 끝나면, callbackFunction을 이미지 데이터와 함께 호출합니다. startBackgroundDownload(imageUrl, callbackFunction) function 이미지_다운로드_완료시(image_data): print("이미지 다운로드 완료!") displayImage(image_data) print("프로그램 시작...") 이미지_다운로드_시작("http://example.com/large_image.jpg", 이미지_다운로드_완료시) // 즉시 다음 코드로 넘어감 print("이미지 다운로드하는 동안 다른 작업 수행 중...") doSomeOtherWork() print("다른 작업 끝.") // 잠시 후, 다운로드가 완료되면 '이미지_다운로드_완료시' 함수가 호출됩니다.이 코드에서는 "프로그램 시작...", "다른 작업 수행 중...", "다른 작업 끝."이 먼저 출력될 가능성이 높습니다. "이미지 다운로드 완료!"는 다운로드가 끝나는 미래의 특정 시점에 출력됩니다.
-
시각적 표현:

동기 (Synchronous) vs. 비동기 (Asynchronous)
이 구분은 작업의 완료를 어떻게 조율하는지, 그리고 호출자가 결과를 기다리는지에 대한 이야기입니다.
동기 (Synchronous) ⏳
동기 방식은 작업이 순서대로, 단계별로 수행되는 것을 의미합니다. 각 작업은 바로 앞의 작업이 완전히 끝나야만 시작될 수 있습니다. 함수를 호출한 쪽에서는 그 함수의 결과가 반환될 때까지 기다립니다.
-
비유: 당신(셰프)이 레시피를 보고 요리합니다. "1. 양파 썰기", "2. 양파 볶기", "3. 토마토 넣기" 순서가 있다면, 당신은 양파를 다 썰기 전에는 볶지 않고, 양파를 다 볶기 전에는 토마토를 넣지 않습니다. 각 단계는 이전 단계가 끝나기를 기다립니다.
-
특징:
-
순서 보장: 작업이 예측 가능한 순서대로 완료됨을 보장합니다.
-
단순한 제어 흐름: 코드의 실행 순서를 이해하기 쉽습니다.
-
결과 대기: 호출한 함수는 호출된 함수가 결과를 반환할 때까지 대기합니다.
-
-
블로킹과의 관계: 동기 작업은 블로킹 I/O를 사용해 구현되는 경우가 많습니다. 동기 함수가 I/O 작업을 수행한다면, 프로그램은 그 I/O가 끝나고 결과가 반환될 때까지 블로킹됩니다.
-
시각적 표현:

비동기 (Asynchronous) ⚡
비동기 방식은 어떤 작업을 시작시킨 후, 그 작업이 완료되기를 기다리지 않고 즉시 다음 코드를 실행하는 것을 의미합니다. 비동기 작업이 나중에 완료되면, 프로그램에게 완료 사실을 알립니다. (예: 콜백 함수 호출, Promise 이행, 이벤트 발생 등)
-
비유: 당신(셰프)이 보조 셰프에게 "이 야채 좀 썰어줘. 다 되면 알려줘."라고 말합니다. 그리고 당신은 즉시 소스를 만들기 시작합니다. 보조 셰프가 야채를 다 썰면 당신에게 와서 알려주고 썬 야채를 건네줍니다.
-
특징:
-
본질적으로 논블로킹: 비동기 작업은 호출자가 결과를 기다리지 않으므로 항상 논블로킹입니다.
-
이벤트/콜백 기반: 작업 완료를 관리하기 위해 이벤트 루프, 콜백, 프로미스, async/await 같은 패턴을 사용합니다.
-
응답성 및 확장성 향상: 프로그램이 멈추지 않고 여러 I/O 작업을 동시에 처리할 수 있어, 특히 단일 스레드 환경(예: Node.js)에서 높은 확장성을 제공합니다.
-
복잡성 증가: 콜백 지옥(callback hell)이나 경쟁 상태(race condition) 같은 문제가 발생할 수 있으며, 디버깅이 더 어려워질 수 있습니다.
-
-
논블로킹과의 관계: 비동기 작업은 항상 논블로킹입니다. 비동기라는 말 자체가 '즉시 완료되기를 기다리지 않는다'는 의미를 내포하기 때문입니다.
-
시각적 표현:

개념 간의 관계 정리
이 개념들은 서로 밀접하게 연관되어 있습니다.
-
블로킹(Blocking)은 동기(Synchronous)입니다. 작업이 블로킹된다는 것은 호출한 쪽에서 그 작업이 끝날 때까지 기다린다는 뜻이므로, 이는 동기적인 흐름입니다.
-
논블로킹(Non-Blocking)은 비동기(Asynchronous)입니다. 작업이 논블로킹이라는 것은 호출한 쪽에서 기다리지 않는다는 뜻이므로, 결과를 나중에 처리할 비동기적인 메커니즘이 반드시 필요합니다.
일반적인 조합:
-
동기 & 블로킹 (가장 흔함):
-
호출자가 작업을 요청하고, 작업이 끝나 결과를 받을 때까지 멈춰서 기다립니다.
-
예시: 대부분의 언어에서 기본적인 파일 읽기(
file.read()) 함수.
-
-
비동기 & 논블로킹 (응답성과 동시성에 이상적):
-
호출자가 작업을 요청하고, 즉시 다른 일을 계속하며, 나중에 작업이 완료되면 통보를 받아 결과를 처리합니다.
-
예시: 웹 브라우저의
fetchAPI, Node.js의 거의 모든 I/O, Python의asyncio.
-
이 개념들이 왜 중요할까요?
-
애플리케이션 응답성: GUI 애플리케이션(웹, 데스크톱)에서 블로킹 작업 하나가 전체 프로그램을 멈추게 할 수 있습니다. 비동기/논블로킹은 부드러운 사용자 경험을 위해 필수적입니다.
-
서버 확장성: 웹 서버처럼 수많은 동시 요청을 처리해야 하는 경우, 비동기/논블로킹 모델은 적은 리소스(스레드)로 더 많은 요청을 효율적으로 처리할 수 있게 해줍니다.
-
리소스 활용: 블로킹 방식은 I/O를 기다리는 동안 CPU를 낭비하지만, 논블로킹 방식은 그 시간에 다른 유용한 작업을 수행하여 CPU 활용률을 극대화합니다.
결론
간단히 요약하자면:
-
블로킹 / 논블로킹: I/O 작업을 기다리는 동안 호출한 스레드가 멈추는가, 계속 진행하는가의 관점.
-
동기 / 비동기: 작업 결과를 그 자리에서 바로 받을 것인가, 나중에 통보받을 것인가의 관점.
실제 프로그래밍에서는 주로 동기+블로킹 방식과 비동기+논블로킹 방식의 두 가지 조합을 중심으로 코드를 작성하게 됩니다. 이 두 가지 방식의 차이를 정확히 이해하면 더 빠르고 효율적인 애플리케이션을 만드는 데 큰 도움이 될 것입니다.
references